package com.zws.cucumber.bootstrap.cass;

import org.apache.commons.io.FileUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

import static com.zws.cucumber.bootstrap.ZwsCassBootstrap.BACKDOOR_PORT;
import static com.zws.cucumber.bootstrap.ZwsCassBootstrap.CASSANDRA_PORT;
import static com.zws.cucumber.bootstrap.cass.ZwsBootstrapUtil.*;


public final class ZwsStandaloneCassWindowsBootstrap extends ZwsAbstractCassBootstrap {

    private static final String DATACENTER = "datacenter1";
    public static final String PATTERN = "classpath*:bootstrap-scripts/cassandra/%s";
    public static final String PREPARE_ENV_FILE = "prepareEnv.bat";
    public static final String PREPARE_CASSANDRA_FILE = "prepareCassandra.bat";
    public static final String PATTERN_PREPARE_ENV = PATTERN.formatted(PREPARE_ENV_FILE);
    public static final String PATTERN_PREPARE_CASSANDRA = PATTERN.formatted(PREPARE_CASSANDRA_FILE);
    public static final String PARENT_DIRECTORY = "C:\\zws-test-cassandra3";

    private String directory = null;

    @Override
    public int getCassandraPort() {
        return CASSANDRA_PORT;
    }

    @Override
    public int getBackdoorPort() {
        return BACKDOOR_PORT;
    }

    @Override
    public String getPathOfSystemLogs() {
        return PARENT_DIRECTORY + "\\" + directory + "\\logs\\system.log";
    }

    @Override
    public void prepareAndStartCassandra0() throws Exception {
        mkdir();
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

        prepareCassandra(resolver);
        startCassandra(resolver);
    }

    private void mkdir() {
        try {
            FileUtils.forceMkdir(FileUtils.getFile(PARENT_DIRECTORY));
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private String readDirectory(PathMatchingResourcePatternResolver resolver) throws IOException {
        Resource res = resolver.getResources(PATTERN_PREPARE_ENV)[0];
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(res.getInputStream()))) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("set STANDALONE_CASSANDRA_DIR=")) {
                    return line.substring("set STANDALONE_CASSANDRA_DIR=".length());
                }
            }
        }
        return null;
    }

    private void prepareCassandra(PathMatchingResourcePatternResolver resolver) throws IOException, InterruptedException {
        ZwsBootstrapUtil.extractFile(resolver, PATTERN_PREPARE_ENV, PARENT_DIRECTORY + "\\" + PREPARE_ENV_FILE);
        ZwsBootstrapUtil.extractFile(resolver, PATTERN_PREPARE_CASSANDRA, PARENT_DIRECTORY + "\\" + PREPARE_CASSANDRA_FILE);

        String[] envs = getPrepareCassandraEnvsBuilder().toArray();
        Process p1 = Runtime.getRuntime().exec(new String[] { "CMD", "/C", PREPARE_CASSANDRA_FILE }, envs, new File(PARENT_DIRECTORY));
        p1.getInputStream().transferTo(System.out);
        p1.getErrorStream().transferTo(System.err);
        int exitCode = p1.waitFor();
        System.out.println("exitCode: " + exitCode);
        if (exitCode != 0) {
            throw new RuntimeException("Failed to prepare Cassandra, exitCode: %d".formatted(exitCode));
        }
    }

    private void startCassandra(PathMatchingResourcePatternResolver resolver) throws IOException, InterruptedException {
        startTimer();
        directory = readDirectory(resolver);

        FileUtils.deleteDirectory(new File(PARENT_DIRECTORY + "\\" + directory + "\\logs"));
        FileUtils.deleteDirectory(new File(PARENT_DIRECTORY + "\\" + directory + "\\data"));

        String[] envs = getStartCassandraEnvsBuilder()
                .withEnv("JAVA_OPTS", String.join(
                        " ",
                        "-Xmx512M",
                        "-Xms512M",
                        "-Xmn384M",
                        "-Xss256k",
                        "-XX:+UseTLAB",
                        "-XX:TLABSize=128k",
                        "-XX:ReservedCodeCacheSize=64M",
                        "-XX:-UseCompressedOops",
                        "-XX:-UseBiasedLocking",
                        "-XX:+UseParallelGC",
                        "-XX:SurvivorRatio=8",
                        "-XX:+AlwaysPreTouch",
                        "-XX:-UseAdaptiveSizePolicy",
                        "-XX:+HeapDumpOnOutOfMemoryError",
                        "-XX:HeapDumpPath=\"" + PARENT_DIRECTORY + "\\" + directory + "\\logs\\heapdump.hprof\"",
                        "-XX:CompileCommandFile=\"" + PARENT_DIRECTORY + "\\" + directory + "\\conf\\hotspot_compiler\""))
                .withEnv("CASSANDRA_PARAMS", String.join(
                        " ",
                        "-Dcassandra.unsafesystem=true",
                        "-Dcassandra.native.epoll.enabled=false",
                        "-Dcassandra.custom_query_handler_class=\"zws.common.TestQueryHandler\""))
                .toArray();

        Process proc = Runtime.getRuntime().exec(
                new String[] { "CMD", "/C", "cassandra.bat" },
                envs,
                new File(PARENT_DIRECTORY + "\\" + directory + "\\" + "bin"));
        handleStdoutAndStderr(proc);

        String pid = waitAndGetPid();
        makeKillCassandraBat(pid);
        println("standalone cassandra: started, elapsed: %dms".formatted(elapsed()));
    }

    private void makeKillCassandraBat(String pid) {
        final String cmd = "taskkill /F /PID " + pid;
        final String filepath = PARENT_DIRECTORY + "\\kill-cassandra.bat";
        try { ZwsBootstrapUtil.makeFile(filepath, cmd); } catch (Exception e) { e.printStackTrace(); }
    }

}
